using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.StylusInput;
using Microsoft.StylusInput.PluginData;
using System.Drawing;
using Microsoft.Ink;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.IO;

namespace Woz
{
    /// <summary>
    /// the pen tool class for drawing strokes on the canvas
    /// </summary>
    public class PenTool : Woz.Tool
    {
        /// <summary>
        /// initializes static variables
        /// </summary>
        static PenTool()
        {
            Woz.PenTool.sFineThicknessText = "Fine";
            Woz.PenTool.sNormalThicknessText = "Normal";
            Woz.PenTool.sBoldThicknessText = "Bold";
            Woz.PenTool.sPenWidthDropDownText = "Pen Thickness Drop Down";
        }

        /// <summary>
        /// default constructor
        /// </summary>
        /// <param name="pControl">the canvas control to draw strokes on</param>
        public PenTool(Control pCanvasControl) : base(pCanvasControl)
        {
            // create the stroke pen which is managed by the resource manager
            this.mCurrentPen = Woz.ResourceManager.Instance.GetPen(Woz.StaticVariables.Instance.InitialPenColor, Woz.StaticVariables.Instance.InitialPenThickness);

            // initialize the current stroke points
            this.mCurrentStrokePoints = new List<Point>();

            // create the options panel
            this.mOptionsPanel = new Woz.PenOptionsPanel();

            // add the toolstrips we want to our list
            this.mToolStripNames.Add("pen thickness");
            this.mToolStripNames.Add("color");

            // set up the valid modes
            this.mValidModes.Add(Woz.StaticVariables.Instance.DesignScreens);

            // set the initial pen thickness
            this.mCurrentThickness = Woz.PenTool.sNormalThicknessText;
        }

        /// <summary>
        /// the name of this tool
        /// </summary>
        public override string Name
        {
            get 
            {
                return "Pen";
            }
        }

        /// <summary>
        /// returns the name of the icon file
        /// </summary>
        public override string IconFileName
        {
            get 
            { 
                return "icon_pen.png"; 
            }
        }

        /// <summary>
        /// handles selection of tool
        /// </summary>
        protected override void OnProcessSelection()
        {
            // well use this to cast items in the loop into this type
            ToolStripButton toolStripButton;

            // first make sure the color toolstrip exists
            if (Woz.InterfaceManager.Instance.ToolStrips.ContainsKey("color"))
            {

                // add callbacks to all the color buttons
                foreach (ToolStripItem toolStripItem in Woz.InterfaceManager.Instance.ToolStrips["color"].Items)
                {
                    // cast the toolstripitem into the toolstrip button
                    toolStripButton = toolStripItem as ToolStripButton;

                    // if the previous cast failed then it would have returned a null
                    if (toolStripButton == null)
                    {
                        continue;
                    }

                    // check if there is a current color toolstrip button
                    if (this.mCurrentColorToolStripButton == null)
                    {
                        // set the current color toolstrip button to the black button if it exists 
                        if (Woz.InterfaceManager.Instance.Buttons.ContainsKey("Black"))
                        {
                            this.mCurrentColorToolStripButton = Woz.InterfaceManager.Instance.Buttons["Black"];
                        }
                    }

                    // add callbacks to the woz buttons instead of just these toolstrip button
                    Woz.InterfaceManager.Instance.Buttons[toolStripButton.Name].Click += this.cColorButtonClick;
                }

                // there is a current color toolstrip button so check it
                if (this.mCurrentColorToolStripButton != null)
                {
                    this.mCurrentColorToolStripButton.Checked = true;
                }
            }

            // first make sure the pen width drop down exists
            if (Woz.InterfaceManager.Instance.DropDownButtons.ContainsKey(Woz.PenTool.sPenWidthDropDownText))
            {
                // add a callback to the interface manager for this
                Woz.InterfaceManager.Instance.DropDownItemClicked += this.cPenWidthDropDownItemClicked;

                // check the proper item that we want
                if (Woz.InterfaceManager.Instance.DropDownButtons.ContainsKey(Woz.PenTool.sPenWidthDropDownText))
                {
                    // get the drop down button from the interface manager
                    ToolStripDropDownButton dropDownButton = (ToolStripDropDownButton)Woz.InterfaceManager.Instance.DropDownButtons[Woz.PenTool.sPenWidthDropDownText];
                    
                    // see if this drop down button has a child with this name
                    if (dropDownButton.DropDownItems.ContainsKey(this.mCurrentThickness))
                    {
                        // get the menu item
                        ToolStripMenuItem menuItem = (ToolStripMenuItem)dropDownButton.DropDownItems[this.mCurrentThickness];

                        // invoke the checking event on this menu item
                        Woz.InterfaceManager.Instance.InvokeDropDownButtonItemClick(dropDownButton, menuItem);
                    }
                }
            }

            // set the cursor to the current 
            this.UpdatePenCursor();
        }

        /// <summary>
        /// handles deselection of tool
        /// </summary>
        protected override void OnProcessDeselection()
        {
            // well use this to cast items in the loop into this type
            ToolStripButton toolStripButton;

            // remove callbacks to all the color buttons
            if (Woz.InterfaceManager.Instance.ToolStrips.ContainsKey("color"))
            {
                foreach (ToolStripItem toolStripItem in Woz.InterfaceManager.Instance.ToolStrips["color"].Items)
                {
                    // cast the toolstripitem into the toolstrip button
                    toolStripButton = toolStripItem as ToolStripButton;

                    // if the previous cast failed then it would have returned a null
                    if (toolStripButton == null)
                    {
                        continue;
                    }

                    // tell this button to be unchecked
                    toolStripButton.Checked = false;

                    // remove callbacks to the woz buttons instead of just these toolstrip button
                    Woz.InterfaceManager.Instance.Buttons[toolStripButton.Name].Click -= this.cColorButtonClick;
                }
            }

            // first make sure the pen width drop down exists
            if (Woz.InterfaceManager.Instance.DropDownButtons.ContainsKey(Woz.PenTool.sPenWidthDropDownText))
            {
                // remove the callback to the interface manager for this
                Woz.InterfaceManager.Instance.DropDownItemClicked -= this.cPenWidthDropDownItemClicked;
            }

            // reset the cursor back to an arrow
            base.SetCurrentCursor(Woz.ResourceManager.Instance.Cursors["arrow"]);
        }

        /// <summary>
        /// reset the pen tool
        /// </summary>
        public override void Reset()
        {
        }

        /// <summary>
        /// sets the current pen thickness
        /// </summary>
        /// <param name="pThickness">the thickness amount</param>
        private void SetThickness(float pThickness)
        {
            // set the current pen to this width
            this.mCurrentPen = Woz.ResourceManager.Instance.GetPen(this.mCurrentPen.Color, pThickness);

            // update the pen cursor
            this.UpdatePenCursor();
        }

        /// <summary>
        /// sets the current pen color
        /// </summary>
        /// <param name="pColor">the color value</param>
        private void SetColor(Color pColor)
        {
            // set the current pen to this color
            this.mCurrentPen = Woz.ResourceManager.Instance.GetPen(pColor, this.mCurrentPen.Width);

            // update the pen cursor
            this.UpdatePenCursor();
        }

        /// <summary>
        /// handles the stylus being pressed against the screen
        /// </summary>
        /// <param name="pLocation">the location which the stylus was pressed</param>
        protected override void OnStylusDown(Point pLocation)
        {
             // clear the list of stroke points
            this.mCurrentStrokePoints.Clear();

            // save the last point
            this.mLastPoint = pLocation;

            // add this point to the list points in our stroke
            this.mCurrentStrokePoints.Add(pLocation);

            // initialize a new set of graphics
            this.InitializeGraphics();

            // set the smoothing mode
            this.mGraphics.SmoothingMode = SmoothingMode.AntiAlias;
        }

        /// <summary>
        /// handles the stylus moving around while pressed down on the screen
        /// </summary>
        /// <param name="pLocations">the locations the stylus has been to</param>
        protected override void OnPackets(Point[] pLocations)
        {
            foreach (Point point in pLocations)
            {
                // add this point to our list points in the stroke
                if ((point.X != this.mLastPoint.X) || (point.Y != this.mLastPoint.Y))
                {
                    this.mCurrentStrokePoints.Add(point);

                    // draw the line
                    this.mGraphics.DrawLine(this.mCurrentPen, this.mLastPoint, point);
                }

                // set the last point to this one
                this.mLastPoint = point;
            }

            // update the pen cursor
            this.UpdatePenCursor();
        }

        /// <summary>
        /// handles the stylus moving around near the screen
        /// </summary>
        /// <param name="pLocations">the locations the stylus has been to</param>
        protected override void OnInAirPackets(Point[] pLocations)
        {
            // update the pen cursor
            this.UpdatePenCursor();
        }

        /// <summary>
        /// handles the stylus being released from the screen
        /// </summary>
        /// <param name="pLocation">the location which the stylus was released</param>
        protected override void OnStylusUp(Point pLocation)
        {
            // also add it to the list of points in this stroke
            this.mCurrentStrokePoints.Add(pLocation);

            // draw the last little line
            this.mGraphics.DrawLine(this.mCurrentPen, this.mLastPoint, pLocation);

            // release the graphics
            this.ReleaseGraphics();

            // now create the add stroke action using the action manager 
            Woz.Action action = Woz.ActionManager.Instance.CreateAction(typeof(Woz.AddStrokeAction), this.mCurrentStrokePoints.ToArray(), this.mCurrentPen);

            // execute the action we just created
            action.Execute();
        }

        /// <summary>
        /// handles the color button being clicked
        /// </summary>
        /// <param name="pButton">the button that was clicked</param>
        private void cColorButtonClick(Woz.Button pButton)
        {
            // a flag for whether or not the custom color button exists
            bool customColorButtonExists = Woz.InterfaceManager.Instance.Buttons.ContainsKey("Custom Color");

            // check if they clicked on the color chooser
            if (pButton.Name == "Color Chooser")
            {
                // create some arguments that well pass to the color dialog
                Woz.ResourceManager.ColorDialogArgs args = new Woz.ResourceManager.ColorDialogArgs();
                args.FullOpen = true;
                if(customColorButtonExists)
                {
                    args.Color = Woz.Utilities.Instance.GetColor(Woz.InterfaceManager.Instance.Buttons["Custom Color"].Background);
                }

                // tell the resource manager to show the color dialog
                Woz.ResourceManager.Instance.ColorDialogMethod.Invoke(this, args);
                
                // make sure there was an okay
                if (args.DialogResult == DialogResult.OK)
                {
                    // set the background color in the custom color button to this color
                    if (customColorButtonExists)
                    {
                        // first build a string out of the new color
                        string color = args.Color.R.ToString() + ", " + args.Color.G.ToString() + ", " + args.Color.B.ToString();
                        
                        // now set the actual color on the button
                        Woz.InterfaceManager.Instance.Buttons["Custom Color"].Background = color;

                        // finally invoke a click on that button by directly calling this callback
                        this.cColorButtonClick(Woz.InterfaceManager.Instance.Buttons["Custom Color"]);
                    }
                }
            }

            // or check if its actually a color
            else
            {
                // its actually a color so set the pen color to this
                this.SetColor(Woz.Utilities.Instance.GetColor(pButton.Background));

                // uncheck the current color toolstrip button if it exists
                if (this.mCurrentColorToolStripButton != null)
                {
                    this.mCurrentColorToolStripButton.Checked = false;
                }

                // set this as the current color toolstrip button
                this.mCurrentColorToolStripButton = pButton;

                // check this button
                this.mCurrentColorToolStripButton.Checked = true;
            }
        }

        /// <summary>
        /// update the cursor of the pen tool
        /// </summary>
        private void UpdatePenCursor()
        {
            // calculate the pen thickness using the transformation
            float penThickness = this.mCurrentPen.Width;
            PointF[] pts = new PointF[] { new PointF(10.0f, 0), new PointF(10.0f + penThickness, 0) };
            Woz.DynamicVariables.Instance.TransformationMatrix.TransformPoints(pts);
            penThickness = Math.Max((pts[1].X - pts[0].X), 60.0f);

            // get the cursor name
            string cursorName = String.Format("pen_{0}_{1}", this.mCurrentPen.Color.ToArgb().ToString("x"), (int)(penThickness * 100.0f));
            
            // add the cursor if it does not exist
            if (!Woz.ResourceManager.Instance.Cursors.ContainsKey(cursorName))
            {
                // create the bitmap
                int pixelPenThickness = Woz.InkSpaceConverter.Instance.InkSpaceToPixel(new Point((int)(penThickness + 0.5f), 0)).X;
                using (Bitmap bmp = new Bitmap(pixelPenThickness + 2, pixelPenThickness + 2))
                {
                    // populate the bitmap
                    using (Graphics gfx = Woz.InkSpaceConverter.Instance.InitializeInkGraphics(Graphics.FromImage(bmp)))
                    {
                        float inkSpaceOnePixel = Woz.InkSpaceConverter.Instance.InkSpaceTenPixels / 10.0f;
                        Brush cursorBrush = new SolidBrush(this.mCurrentPen.Color);
                        gfx.SmoothingMode = SmoothingMode.HighQuality;
                        gfx.FillEllipse(cursorBrush, inkSpaceOnePixel, inkSpaceOnePixel, penThickness, penThickness);
                        cursorBrush.Dispose();
                    }

                    // add the bitmap as a cursor
                    Woz.ResourceManager.Instance.AddCursor(cursorName, bmp);
                }
            }

            // set the cursor
            base.SetCurrentCursor(Woz.ResourceManager.Instance.Cursors[cursorName]);
        }

        private void cPenWidthDropDownItemClicked(object pSender, ToolStripItemClickedEventArgs pEventArgs)
        {
            // save the clicked item
            ToolStripItem menuItem = pEventArgs.ClickedItem;

            // merely handle which text is attached to the clicked button
            if (menuItem.Text == Woz.PenTool.sFineThicknessText)
            {
                this.SetThickness(Woz.StaticVariables.Instance.InitialPenThickness / 1.8f);
            }

            else if (menuItem.Text == Woz.PenTool.sNormalThicknessText)
            {
                this.SetThickness(Woz.StaticVariables.Instance.InitialPenThickness);
            }

            else if (menuItem.Text == Woz.PenTool.sBoldThicknessText)
            {
                this.SetThickness(Woz.StaticVariables.Instance.InitialPenThickness * 1.8f);
            }

            // remember this as the thickness text
            this.mCurrentThickness = menuItem.Text;
        }
        
        /// <summary>
        /// the current selected pen on the pen tool
        /// </summary>
        /// <remarks>this pen is used only by the pen tool and so its not managed by the resource manager</remarks>
        private Pen mCurrentPen;

        /// <summary>
        /// well use this to store the points as were putting them down
        /// </summary>
        private List<Point> mCurrentStrokePoints;

        /// <summary>
        /// save the last point so we dont have to do a lookup
        /// </summary>
        private Point mLastPoint;

        /// <summary>
        /// save the current color toolstrip button
        /// </summary>
        private Woz.Button mCurrentColorToolStripButton;

        /// <summary>
        /// string representation of the current pen thickness
        /// </summary>
        private string mCurrentThickness;

        /// <summary>
        /// string representations of all the different terminals we may read from xml
        /// </summary>
        private static string sPenWidthDropDownText;
        private static string sFineThicknessText;
        private static string sNormalThicknessText;
        private static string sBoldThicknessText;
    }
}
